{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# Python描述符\n",
    "\n",
    "Python 中，通过使用描述符，可以让程序员在引用一个对象属性时自定义要完成的工作。\n",
    "在每次查找属性时，描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用（注意不要和 __getattr__() 弄混）。也就是说，每次使用类对象.属性（或者 getattr(类对象，属性值)）的调用方式时，都会隐式地调用 __getattribute__()，它会按照下列顺序查找该属性：\n",
    "1. 验证该属性是否为类实例对象的数据描述符；\n",
    "2. 如果不是，就查看该属性是否能在类实例对象的 __dict__ 中找到；\n",
    "3. 最后，查看该属性是否为类实例对象的非数据描述符。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Retrieving  var \"x\"\n",
      "10\n",
      "updating  var \"x\"\n",
      "Retrieving  var \"x\"\n",
      "20\n",
      "5\n"
     ]
    }
   ],
   "source": [
    "class RevealAccess(object):\n",
    "    def __init__(self, initval = None, name = 'var'):\n",
    "        self.val = initval\n",
    "        self.name = name\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        print(\"Retrieving \", self.name)\n",
    "        return self.val\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        print(\"updating \", self.name)\n",
    "        self.val = value\n",
    "\n",
    "class MyClass(object):\n",
    "    x = RevealAccess(10, 'var \"x\"')\n",
    "    y = 5\n",
    "\n",
    "m = MyClass()\n",
    "print(m.x)\n",
    "\n",
    "m.x = 20\n",
    "print(m.x)\n",
    "print(m.y)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "我们在类中定义的方法，其实本身就是一个非数据描述符。\n",
    "在一个类中，如果存在相同名字的属性和方法，按照上面所讲的 __getattribute__ 中查找属性的顺序，这个属性就会优先从实例中获取，如果实例中不存在，才会从非数据描述符中获取\n",
    "到这里我们可以总结一下关于描述符的相关知识点：\n",
    "1. 描述符必须是一个类属性\n",
    "2. __getattribute__ 是查找一个属性（方法）的入口\n",
    "3. __getattribute__ 定义了一个属性（方法）的查找顺序：数据描述符、实例属性、非数据描述符、类属性\n",
    "4. 如果我们重写了 __getattribute__ 方法，会阻止描述符的调用\n",
    "5. 所有方法其实都是一个非数据描述符，因为它定义了 __get__"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 使用场景\n",
    "\n",
    "首先我们定义一个校验基类 Validator，在 __set__ 方法中先调用 validate 方法校验属性是否符合要求，然后再对属性进行赋值。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "outputs": [],
   "source": [
    "class Validator:\n",
    "\n",
    "    def __init__(self):\n",
    "        self.data = {}\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        return self.data[instance]\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        self.validate(value)\n",
    "        self.data[instance] = value\n",
    "\n",
    "    def validate(self, value):\n",
    "        pass"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "接下来，我们定义两个校验类，继承 Validator，然后实现自己的校验逻辑"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "outputs": [],
   "source": [
    "class Number(Validator):\n",
    "    def __init__(self, minValue=None, maxValue=None):\n",
    "        super(Number, self).__init__()\n",
    "        self.minValue = minValue\n",
    "        self.maxValue = maxValue\n",
    "\n",
    "    def validate(self, value):\n",
    "        if not isinstance(value, (int, float)):\n",
    "            raise TypeError(f'Expected {value!r} to be an int or float')\n",
    "        if self.minValue is not None and value < self.minValue:\n",
    "            raise ValueError(f'Expected {value!r} to be at least {self.minValue!r}')\n",
    "        if self.maxValue is not None and value > self.maxValue:\n",
    "            raise ValueError(f'Expected {value!r} to be no more than {self.maxValue!r}')\n",
    "\n",
    "class String(Validator):\n",
    "    def __init__(self, minSize=None, maxSize=None):\n",
    "        super(String, self).__init__()\n",
    "        self.minSize = minSize\n",
    "        self.maxSize = maxSize\n",
    "\n",
    "    def validate(self, value):\n",
    "        if not isinstance(value, str):\n",
    "            raise TypeError(f'Expected {value!r} to be an str')\n",
    "        if self.minSize is not None and len(value) < self.minSize:\n",
    "            raise ValueError(f'Expected {value!r} to be no smaller than {self.minSize!r}')\n",
    "        if self.maxSize is not  None and len(value) > self.maxSize:\n",
    "            raise ValueError(f'Expected {value!r} to be no larger than {self.maxSize}')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "最后，我们使用这个校验类："
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "zhangsan 20\n"
     ]
    },
    {
     "ename": "ValueError",
     "evalue": "Expected -3 to be at least 1",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mValueError\u001B[0m                                Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[34], line 17\u001B[0m\n\u001B[0;32m     12\u001B[0m \u001B[38;5;28mprint\u001B[39m(p1\u001B[38;5;241m.\u001B[39mname, p1\u001B[38;5;241m.\u001B[39mage)\n\u001B[0;32m     14\u001B[0m \u001B[38;5;66;03m# 属性不符合规则\u001B[39;00m\n\u001B[0;32m     15\u001B[0m \u001B[38;5;66;03m#p2 = Person('a', 20)\u001B[39;00m\n\u001B[1;32m---> 17\u001B[0m p3 \u001B[38;5;241m=\u001B[39m \u001B[43mPerson\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mlisi\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m-\u001B[39;49m\u001B[38;5;241;43m3\u001B[39;49m\u001B[43m)\u001B[49m\n",
      "Cell \u001B[1;32mIn[34], line 8\u001B[0m, in \u001B[0;36mPerson.__init__\u001B[1;34m(self, name, age)\u001B[0m\n\u001B[0;32m      6\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m__init__\u001B[39m(\u001B[38;5;28mself\u001B[39m, name, age):\n\u001B[0;32m      7\u001B[0m     \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mname \u001B[38;5;241m=\u001B[39m name\n\u001B[1;32m----> 8\u001B[0m     \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mage \u001B[38;5;241m=\u001B[39m age\n",
      "Cell \u001B[1;32mIn[28], line 10\u001B[0m, in \u001B[0;36mValidator.__set__\u001B[1;34m(self, instance, value)\u001B[0m\n\u001B[0;32m      9\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m__set__\u001B[39m(\u001B[38;5;28mself\u001B[39m, instance, value):\n\u001B[1;32m---> 10\u001B[0m     \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mvalidate\u001B[49m\u001B[43m(\u001B[49m\u001B[43mvalue\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m     11\u001B[0m     \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mdata[instance] \u001B[38;5;241m=\u001B[39m value\n",
      "Cell \u001B[1;32mIn[29], line 11\u001B[0m, in \u001B[0;36mNumber.validate\u001B[1;34m(self, value)\u001B[0m\n\u001B[0;32m      9\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mTypeError\u001B[39;00m(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mExpected \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mvalue\u001B[38;5;132;01m!r}\u001B[39;00m\u001B[38;5;124m to be an int or float\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[0;32m     10\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mminValue \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m value \u001B[38;5;241m<\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mminValue:\n\u001B[1;32m---> 11\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mExpected \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mvalue\u001B[38;5;132;01m!r}\u001B[39;00m\u001B[38;5;124m to be at least \u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mminValue\u001B[38;5;132;01m!r}\u001B[39;00m\u001B[38;5;124m'\u001B[39m)\n\u001B[0;32m     12\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mmaxValue \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mand\u001B[39;00m value \u001B[38;5;241m>\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mmaxValue:\n\u001B[0;32m     13\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mExpected \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mvalue\u001B[38;5;132;01m!r}\u001B[39;00m\u001B[38;5;124m to be no more than \u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mmaxValue\u001B[38;5;132;01m!r}\u001B[39;00m\u001B[38;5;124m'\u001B[39m)\n",
      "\u001B[1;31mValueError\u001B[0m: Expected -3 to be at least 1"
     ]
    }
   ],
   "source": [
    "class Person(object):\n",
    "    # 定义属性的校验规则，内部使用描述符实现\n",
    "    name = String(minSize=3, maxSize=10)\n",
    "    age = Number(minValue=1, maxValue=120)\n",
    "\n",
    "    def __init__(self, name, age):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "\n",
    "# 属性符合规则\n",
    "p1 = Person('zhangsan', 20)\n",
    "print(p1.name, p1.age)\n",
    "\n",
    "# 属性不符合规则\n",
    "#p2 = Person('a', 20)\n",
    "\n",
    "p3 = Person(\"lisi\", -3)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## function与method\n",
    "\n",
    "从结果我们可以看出它们的区别：\n",
    "* function 准确来说就是一个函数，并且它实现了 __get__ 方法，因此每一个 function 都是一个非数据描述符，而在类中会把 function 放到 __dict__ 中存储\n",
    "* 当 function 被实例调用时，它是一个 bound method\n",
    "* 当 function 被类调用时， 它是一个 unbound method"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<function A.foo at 0x000002256BBC1120>\n",
      "<function A.foo at 0x000002256BBC1120>\n",
      "<bound method A.foo of <__main__.A object at 0x000002256BAB2830>>\n"
     ]
    }
   ],
   "source": [
    "class A(object):\n",
    "\n",
    "    def foo(self):\n",
    "        return 'xyz'\n",
    "\n",
    "print(A.__dict__['foo'])\n",
    "print(A.foo)\n",
    "print(A().foo)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}